using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

namespace Supremacy.Client.Controls
{
    public abstract class GameControlBase : Control, IGameControl, ILogicalParent
    {
        #region Fields
        private readonly GameControlService.GameControlFlagManager _flags;
        #endregion

        #region Routed Events
        public static readonly RoutedEvent ClickEvent;
        public static readonly RoutedEvent PreviewClickEvent;
        #endregion

        #region Dependency Properties
        public static readonly DependencyProperty CommandProperty;
        public static readonly DependencyProperty CommandParameterProperty;
        public static readonly DependencyProperty CommandTargetProperty;
        public static readonly DependencyProperty HasImageProperty;
        public static readonly DependencyProperty HasLabelProperty;
        public static readonly DependencyProperty IdProperty;
        public static readonly DependencyProperty ImageSourceLargeProperty;
        public static readonly DependencyProperty ImageSourceSmallProperty;
        public static readonly DependencyProperty IsHighlightedProperty;
        public static readonly DependencyProperty LabelProperty;
        public static readonly DependencyProperty VariantSizeProperty;
        public static readonly DependencyProperty ContextProperty;
        #endregion

        #region Events
        public event EventHandler<ExecuteRoutedEventArgs> Click
        {
            add { base.AddHandler(ClickEvent, value); }
            remove { base.RemoveHandler(ClickEvent, value); }
        }

        public event EventHandler<ExecuteRoutedEventArgs> PreviewClick
        {
            add { base.AddHandler(PreviewClickEvent, value); }
            remove { base.RemoveHandler(PreviewClickEvent, value); }
        }
        #endregion

        #region Constructors and Finalizers
        static GameControlBase()
        {
            ClickEvent = GameControlService.ClickEvent.AddOwner(typeof(GameControlBase));
            PreviewClickEvent = GameControlService.PreviewClickEvent.AddOwner(typeof(GameControlBase));

            CommandProperty = GameControlService.CommandProperty.AddOwner(typeof(GameControlBase));
            CommandParameterProperty = GameControlService.CommandParameterProperty.AddOwner(typeof(GameControlBase));
            CommandTargetProperty = GameControlService.CommandTargetProperty.AddOwner(typeof(GameControlBase));
            HasImageProperty = GameControlService.HasImageProperty.AddOwner(typeof(GameControlBase));
            HasLabelProperty = GameControlService.HasLabelProperty.AddOwner(typeof(GameControlBase));
            IdProperty = GameControlService.IdProperty.AddOwner(typeof(GameControlBase));
            ImageSourceLargeProperty = GameControlService.ImageSourceLargeProperty.AddOwner(typeof(GameControlBase));
            ImageSourceSmallProperty = GameControlService.ImageSourceSmallProperty.AddOwner(typeof(GameControlBase));
            IsHighlightedProperty = GameControlService.IsHighlightedProperty.AddOwner(typeof(GameControlBase));
            LabelProperty = GameControlService.LabelProperty.AddOwner(typeof(GameControlBase));

            VariantSizeProperty = GameControlService.VariantSizeProperty.AddOwner(
                typeof(GameControlBase),
                new FrameworkPropertyMetadata(
                    VariantSize.Medium,
                    OnVariantSizePropertyValueChanged));

            ContextProperty = GameControlService.ContextProperty.AddOwner(
                typeof(GameControlBase),
                new FrameworkPropertyMetadata(OnContextPropertyValueChanged));
        }

        protected GameControlBase()
        {
            _flags = new GameControlService.GameControlFlagManager();
        }
        #endregion

        #region ILogicalParent Implementation
        void ILogicalParent.AddLogicalChild(object child)
        {
            this.AddLogicalChild(child);
        }

        void ILogicalParent.RemoveLogicalChild(object child)
        {
            this.RemoveLogicalChild(child);
        }
        #endregion

        #region IGameControl Implementation
        bool IGameControl.CanUpdateCanExecuteWhenHidden
        {
            get { return this.CanUpdateCanExecuteWhenHidden; }
        }

        object IGameControl.CoerceCommandParameter(DependencyObject obj, object value)
        {
            return this.CoerceCommandParameter(obj, value);
        }

        EventHandler IGameControl.CommandCanExecuteHandler { get; set; }

        GameControlService.GameControlFlagManager IGameControl.Flags
        {
            get { return _flags; }
        }

        bool IGameControl.HasImage
        {
            get { return this.HasImage; }
            set { this.HasImage = value; }
        }

        bool IGameControl.HasLabel
        {
            get { return this.HasLabel; }
            set { this.HasLabel = value; }
        }

        void IGameControl.OnCanExecuteChanged(object sender, EventArgs e)
        {
            this.UpdateCanExecute();
        }

        void IGameControl.OnCommandChanged(ICommand oldCommand, ICommand newCommand)
        {
            this.OnCommandChanged(oldCommand, newCommand);
        }

        void IGameControl.OnCommandUIProviderPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            GameControlService.UpdateUIFromCommand(this);
        }

        void IGameControl.UpdateCanExecute()
        {
            this.UpdateCanExecute();
        }

        void IGameControl.OnCommandParameterChanged(object oldValue, object newValue)
        {
            this.OnCommandParameterChanged(oldValue, newValue);
        }
        #endregion

        #region Properties
        protected virtual bool CanUpdateCanExecuteWhenHidden
        {
            get { return false; }
        }
        
        public ICommand Command
        {
            get { return (ICommand)this.GetValue(CommandProperty); }
            set { this.SetValue(CommandProperty, value); }
        }

        public object CommandParameter
        {
            get { return this.GetValue(CommandParameterProperty); }
            set { this.SetValue(CommandParameterProperty, value); }
        }

        public IInputElement CommandTarget
        {
            get { return (IInputElement)this.GetValue(CommandTargetProperty); }
            set { this.SetValue(CommandTargetProperty, value); }
        }

        public bool HasImage
        {
            get { return (bool)this.GetValue(HasImageProperty); }
            private set { this.SetValue(HasImageProperty, value); }
        }

        public bool HasLabel
        {
            get { return (bool)this.GetValue(HasLabelProperty); }
            private set { this.SetValue(HasLabelProperty, value); }
        }

        [Localizability(LocalizationCategory.NeverLocalize)]
        public string Id
        {
            get { return (string)this.GetValue(IdProperty); }
            set { this.SetValue(IdProperty, value); }
        }

        public ImageSource ImageSourceLarge
        {
            get { return (ImageSource)this.GetValue(ImageSourceLargeProperty); }
            set { this.SetValue(ImageSourceLargeProperty, value); }
        }

        public ImageSource ImageSourceSmall
        {
            get { return (ImageSource)this.GetValue(ImageSourceSmallProperty); }
            set { this.SetValue(ImageSourceSmallProperty, value); }
        }

        public bool IsHighlighted
        {
            get { return (bool)this.GetValue(IsHighlightedProperty); }
            set { this.SetValue(IsHighlightedProperty, value); }
        }

        [Localizability(LocalizationCategory.Label)]
        public string Label
        {
            get { return (string)this.GetValue(LabelProperty); }
            set { this.SetValue(LabelProperty, value); }
        }

        public VariantSize VariantSize
        {
            get { return (VariantSize)this.GetValue(VariantSizeProperty); }
            set { this.SetValue(VariantSizeProperty, value); }
        }

        public GameControlContext Context
        {
            get { return (GameControlContext)this.GetValue(ContextProperty); }
            set { this.SetValue(ContextProperty, value); }
        }
        #endregion

        #region Methods
        private static void OnVariantSizePropertyValueChanged(
            DependencyObject obj,
            DependencyPropertyChangedEventArgs e)
        {
            var control = (GameControlBase)obj;
            var oldVariantSize = (VariantSize)e.OldValue;
            var newVariantSize = (VariantSize)e.NewValue;

            control.OnVariantSizeChanged(oldVariantSize, newVariantSize);
        }

        private static void OnContextPropertyValueChanged(
            DependencyObject obj,
            DependencyPropertyChangedEventArgs e)
        {
            var control = (GameControlBase)obj;
            var oldContext = (GameControlContext)e.OldValue;
            var newContext = (GameControlContext)e.NewValue;

            control.OnContextChanged(oldContext, newContext);
        }

        protected virtual object CoerceCommandParameter(DependencyObject obj, object value)
        {
            return value;
        }

        protected override void OnAccessKey(AccessKeyEventArgs e)
        {
            if (e.IsMultiple)
                base.OnAccessKey(e);
            else
                this.RaiseClickEvent(new ExecuteRoutedEventArgs(ExecuteReason.Keyboard));
        }

        protected virtual void OnClick(ExecuteRoutedEventArgs e)
        {
            e.RoutedEvent = ClickEvent;
            e.Source = this;

            this.RaiseEvent(e);

            GameCommand.ExecuteCommandSource(this);
        }

        protected virtual void OnCommandChanged(ICommand oldCommand, ICommand newCommand) {}

        protected virtual void OnCommandParameterChanged(object oldValue, object newValue)
        {
            if (ReferenceEquals(oldValue, newValue))
                return;

            var oldCheckableCommandParameter = oldValue as ICheckableCommandParameter;
            if (oldCheckableCommandParameter != null)
                oldCheckableCommandParameter.InnerParameterChanged -= OnInnerCommandParameterChanged;

            var newCheckableCommandParameter = newValue as ICheckableCommandParameter;
            if (newCheckableCommandParameter != null)
                newCheckableCommandParameter.InnerParameterChanged += OnInnerCommandParameterChanged;
        }

        private void OnInnerCommandParameterChanged(object sender, EventArgs e)
        {
            UpdateCanExecute();
        }

        protected virtual void OnPreviewClick(ExecuteRoutedEventArgs e)
        {
            e.RoutedEvent = PreviewClickEvent;
            e.Source = this;
            this.RaiseEvent(e);
        }

        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            if (e.Property == IsVisibleProperty)
                GameControlService.HookCommands(this, this.Command, this.Command);
            base.OnPropertyChanged(e);
        }

        protected virtual void OnVariantSizeChanged(VariantSize oldVariantSize, VariantSize newVariantSize) {}
        protected virtual void OnContextChanged(GameControlContext oldContext, GameControlContext newContext) {}

        public void RaiseClickEvent(ExecuteRoutedEventArgs e)
        {
            this.OnPreviewClick(e);

            this.Dispatcher.BeginInvoke(
                DispatcherPriority.Render,
                (Action<ExecuteRoutedEventArgs>)OnClick,
                e);
        }

        public override string ToString()
        {
            return this.GetType().Name + "[Label=" + this.Label + "]";
        }

        protected virtual void UpdateCanExecute() { }
        #endregion
    }
}